package com.smart.redisson.lock;

import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 分布锁 切面类
 */
@Aspect
@Component
@Slf4j
public class DistributedLockAspect {
    public static final String DEFAULT_DISTRIBUTED_LOCK_NAME = "distributed:lock";

    @Resource
    RedissonClient redissonClient;

    @Pointcut("@annotation(com.smart.redisson.lock.DistributedLock)")
    public void pointcut() {
    }

    /**
     * @param pjp
     * @return
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) {
        Object proceed = null;
        RLock lock = null;
        try {
            Class<?> clazz = pjp.getTarget().getClass();
            String targetName = clazz.getSimpleName();
            String methodName = pjp.getSignature().getName();
            Class<?>[] parameterTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();
            Method method = clazz.getMethod(methodName, parameterTypes);
            DistributedLock annotation = method.getAnnotation(DistributedLock.class);
            if (annotation != null) {
                // 获取锁的名字
                String lockName = StringUtil.isNullOrEmpty(annotation.value()) ? DEFAULT_DISTRIBUTED_LOCK_NAME : annotation.value();
                lock = getLock(lockName, annotation.type(), annotation.multiNames());
                // 获取锁的超时时间
                long waitTime = annotation.waitTime();
                // 自动释放锁的时间
                long leaseTime = annotation.leaseTime();
                boolean isLock;
                if (waitTime != 0 && leaseTime != 0) {
                    isLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
                } else if (waitTime != 0) {
                    isLock = lock.tryLock(waitTime, TimeUnit.SECONDS);
                } else {
                    isLock = lock.tryLock();
                }
                if (isLock) {
                    proceed = pjp.proceed();
                }
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
        return proceed;
    }


    private RLock getLock(String lockName, DistributedLockType type, String[] names) {
        RLock lock;
        if (type.equals(DistributedLockType.REENTRANT_LOCK)) {
            lock = redissonClient.getLock(lockName);
        } else if (type.equals(DistributedLockType.FAIR_LOCK)) {
            lock = redissonClient.getFairLock(lockName);
        } else if (type.equals(DistributedLockType.MULTI_LOCK)) {
            RLock[] rLock = new RLock[names.length];
            for (int i = 0; i < names.length; i++) {
                rLock[i] = redissonClient.getLock(names[i]);
            }
            lock = new RedissonMultiLock(rLock);
        } else {
            RLock[] rLock = new RLock[names.length];
            for (int i = 0; i < names.length; i++) {
                rLock[i] = redissonClient.getLock(names[i]);
            }
            lock = new RedissonRedLock(rLock);
        }
        return lock;

    }


}
